﻿using System;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Data.Query;

namespace Nvigorate.Test.Common
{
    /// <summary>
    /// Summary description for SearchCriteriaTests
    /// </summary>
    [TestClass]
    public class SearchCriteriaTests
    {
        public SearchCriteriaTests()
        {
            //
            // TODO: Add constructor logic here
            //
            SqlParameter parameter = new SqlParameter();
        }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext
        {
            get
            {
                return testContextInstance;
            }
            set
            {
                testContextInstance = value;
            }
        }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        /*
        #region Criteria Tests

        [TestMethod]
        public void TestBetweenCriteria()
        {
            string result = Criterion.Between(
                                    "birthdate",
                                    DateTime.Parse("06/01/1970").ToShortDateString(),
                                    DateTime.Parse("07/01/1980").ToShortDateString()
                                ).ToString();

            Assert.IsTrue(@"birthdate BETWEEN '6/1/1970' AND '7/1/1980'" == result);
        }

        [TestMethod]
        public void TestEqualCriteria()
        {
            string result = Criterion.IsEqualTo("social", "111-11-1111").ToString();
            Assert.IsTrue(@"social = '111-11-1111'" == result);

            result = Criterion.IsEqualTo("social", null).ToString();
            Assert.IsTrue(@"social IS NULL" == result);

            result = Criterion.IsEqualTo("social", DBNull.Value).ToString();
            Assert.IsTrue(@"social IS NULL" == result);
        }

        [TestMethod]
        public void TestGreaterCriteria()
        {
            string result = Criterion.GreaterThan("age", 20).ToString();
            Assert.IsTrue(@"age > '20'" == result);
        }

        [TestMethod]
        public void TestGreaterOrEqualCriteria()
        {
            string result = Criterion.GreaterOrEqual("age", 21).ToString();
            Assert.IsTrue(@"age >= '21'" == result);
        }

        [TestMethod]
        public void TestInCriteria1()
        {
            string result = Criterion.In("color", "red", "white", "blue").ToString();
            Assert.IsTrue(@"color IN ('red','white','blue')" == result);
        }

        [TestMethod]
        public void TestInCriteria2()
        {
            string[] colors = new[] {"red", "white", "blue"};
            string result = Criterion.In("color", colors).ToString();
            Assert.IsTrue(@"color IN ('red','white','blue')" == result);
        }

        [TestMethod]
        public void TestInCriteria3()
        {
            int[] colors = new[] {1, 2, 3};
            string result = Criterion.In("color", colors).ToString();
            Assert.IsTrue(@"color IN ('1','2','3')" == result);
        }

        [TestMethod]
        public void TestLessCriteria()
        {
            string result = Criterion.LessThan("age", 65).ToString();
            Assert.IsTrue(@"age < '65'" == result);
        }

        [TestMethod]
        public void TestLessOrEqualCriteria()
        {
            string result = Criterion.LessOrEqual("age", 64).ToString();
            Assert.IsTrue(@"age <= '64'" == result);
        }

        [TestMethod]
        public void TestLikeCriteria()
        {
            string result = Criterion.Like("lastName", "Robs").ToString();
            Assert.IsTrue(@"lastName LIKE '%Robs%'" == result);
        }

        [TestMethod]
        public void TestSubstringCriteria()
        {
            string result = Criterion.Substring("Employid", 3, 3, "001").ToString();
            Assert.IsTrue(@"SUBSTRING(Employid, 3, 3) = '001'" == result);
        }

        #endregion

        #region Clause Tests

        [TestMethod]
        public void TestSingleAndClause()
        {
            string result = (Criterion.Like("firstName", "Alex") & Criterion.Like("lastName", "Rob")).ToString();
            Assert.IsTrue("firstName LIKE '%Alex%' AND lastName LIKE '%Rob%'" == result);
        }

        [TestMethod]
        public void TestSingleOrClause()
        {
            string result = (Criterion.Like("firstName", "Alex") | Criterion.IsEqualTo("firstName", "Alex")).ToString();
            Assert.IsTrue("firstName LIKE '%Alex%' OR firstName = 'Alex'" == result);
        }

        [TestMethod]
        public void TestMultiAndClause()
        {
            string result = ((
                                Criterion.Between("birthdate", "6/10/1940", "6/20/1990") &
                                Criterion.IsEqualTo("address", "377 Alnwick Drive")
                            ) &
                            (
                              Criterion.In("lastName", "Wilson", "Robson") &
                              Criterion.IsEqualTo("gender", "m")
                            )).ToString();
            string expectedResult =
                "(birthdate BETWEEN '6/10/1940' AND '6/20/1990' AND address = '377 Alnwick Drive') AND " +
                "(lastName IN ('Wilson','Robson') AND gender = 'm')";
            Assert.AreEqual(expectedResult, result);
        }

        [TestMethod]
        public void TestMultiOrClause()
        {
            string result =
                            ((
                                Criterion.Between("birthdate", "6/10/1940", "6/20/1990") &
                                Criterion.IsEqualTo("address", "377 Alnwick Drive")
                            ) |
                            (
                              Criterion.In("lastName", "Wilson", "Robson") |
                              Criterion.In("middleName", "Scott", "Andrew", "Royce", "Charles", "Douglas")
                            )).ToString();

            string expectedResult =
                "(birthdate BETWEEN '6/10/1940' AND '6/20/1990' AND address = '377 Alnwick Drive') OR " +
                "(lastName IN ('Wilson','Robson') OR middleName IN ('Scott','Andrew','Royce','Charles','Douglas'))";
            Assert.AreEqual(expectedResult, result);
        }

        [TestMethod]
        public void TestCriteriaSerialization()
        {
            SqlParameter parameter = new SqlParameter();

            SearchCriteria criteria = 
                            ((
                                Criterion.Between("birthdate", "6/10/1940", "6/20/1990") &
                                Criterion.IsEqualTo("address", "377 Alnwick Drive")
                            ) |
                            (
                              Criterion.In("lastName", "Wilson", "Robson") |
                              Criterion.In("middleName", "Scott", "Andrew", "Royce", "Charles", "Douglas")
                            ));

            XmlSerializer xmlSerializer = new XmlSerializer(typeof(SearchCriteria));

            MemoryStream stream = new MemoryStream();
            StreamReader reader = new StreamReader(stream);

            XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8);
            xmlSerializer.Serialize(writer, criteria);

            stream.Position = 0;
            string xml = reader.ReadToEnd();

            Assert.AreNotEqual(xml, "");

            stream.Position = 0;
            SearchCriteria newCriteria = (SearchCriteria)xmlSerializer.Deserialize(stream);

            Assert.AreEqual(criteria.ToString(), newCriteria.ToString());
        }
        #endregion

        [TestMethod]
        public void TestRecursiveLambda()
        {
            SearchCriteria criteria =
                ((
                     Criterion.Between("birthdate", "6/10/1940", "6/20/1990") &
                     Criterion.IsEqualTo("address", "377 Alnwick Drive") &
                     (
                        (
                            Criterion.IsEqualTo("state", "Tennessee") | Criterion.IsEqualTo("state", "TN")
                        ) &
                        Criterion.IsEqualTo("city", "Friendsville")
                     )
                 ) |
                 (
                     Criterion.In("lastName", "Wilson", "Robson") |
                     Criterion.In("middleName", "Scott", "Andrew", "Royce", "Charles", "Douglas")
                 ));

            //Should get 7

            List<Criterion> criterion = new List<Criterion>();

            Action<IClause, List<Criterion>> getCriterion1;
            Action<CriteriaClause, List<Criterion>> getCriterion2 = (c, list) => list.Clear();

            getCriterion1 = (ic, list) =>
                                {
                                    if (ic.GetType().Equals(typeof(CriteriaClause)))
                                        getCriterion2((CriteriaClause)ic, list);
                                    else
                                        list.Add((Criterion) ic);
                                };
            getCriterion2 = (c, list) => c.Criteria.ForEach(ic => getCriterion1(ic, list));

            criteria.ForEach(c => getCriterion2(c, criterion));

            Assert.AreEqual(criterion.Count, 7);
        }

        [TestMethod]
        public void TestParameterization()
        {
            SearchCriteria criteria =
                ((
                     Criterion.Between("birthdate", "6/10/1940", "6/20/1990") &
                     Criterion.IsEqualTo("address", "377 Alnwick Drive") &
                     (
                         (
                             Criterion.IsEqualTo("state", "Tennessee") | Criterion.IsEqualTo("state", "TN")
                         ) &
                         Criterion.IsEqualTo("city", "Friendsville")
                     )
                 ) |
                 (
                     Criterion.In("lastName", "Wilson", "Robson") |
                     Criterion.In("middleName", "Scott", "Andrew", "Royce", "Charles", "Douglas")
                 ));

            //Should get 7

            List<Criterion> criterion = new List<Criterion>();

            Action<IClause, List<Criterion>> getFromICriterion;
            Action<CriteriaClause, List<Criterion>> getFromClause = (c, list) => list.Clear();

            getFromICriterion = (ic, list) =>
                                {
                                    if (ic.GetType().Equals(typeof (CriteriaClause)))
                                        getFromClause((CriteriaClause) ic, list);
                                    else
                                        list.Add((Criterion) ic);
                                };
            getFromClause = (c, list) => c.Criteria.ForEach(ic => getFromICriterion(ic, list));

            criteria.ForEach(c => getFromClause(c, criterion));

            List<SqlParameter> parameters = new List<SqlParameter>();

            criterion.ForEach(c => ParameterizeCriterion(c, parameters));

            Assert.AreEqual(parameters.Count, 13);
        }

        private List<SqlParameter> ParameterizeCriterion(Criterion criterion, List<SqlParameter> list)
        {
            SqlParameter last = null;

            int index = list.Count(p => p.ParameterName.Contains(criterion.Field));
            
            List<SqlParameter> parameters = new List<SqlParameter>();

            criterion.Values.ForEach(v =>
                                         {
                                             var p = new SqlParameter
                                                 (
                                                    string.Format("@{0}{1}", criterion.Field, ++index), v
                                                 );
                                             parameters.Add(p);
                                         });
            criterion.Values.Clear();
            criterion.Values.AddRange(parameters.Select(p => p.ParameterName).ToArray());
            list.AddRange(parameters);
            return list;
        }


        [TestMethod]
        public void TestCriterionSerialization()
        {
            SearchCriteria criteria = Criterion.IsEqualTo("Gender", 2);

            XmlSerializer xmlSerializer = new XmlSerializer(typeof(SearchCriteria));

            MemoryStream stream = new MemoryStream();
            StreamReader reader = new StreamReader(stream);

            XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8);
            xmlSerializer.Serialize(writer, criteria);

            stream.Position = 0;
            string xml = reader.ReadToEnd();

            Assert.AreNotEqual(xml, "");

            stream.Position = 0;
            SearchCriteria newCriteria = (SearchCriteria)xmlSerializer.Deserialize(stream);

            Assert.AreEqual(criteria.ToString(), newCriteria.ToString());
        }

        [TestMethod]
        public void TestWCFDeserialization()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-8"" ?><criteria><clause><criteria field=""Gender"" comparison=""Equal""><value>2</value></criteria></clause></criteria>";

            StringReader reader = new StringReader(xml);
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(SearchCriteria));
            SearchCriteria newCriteria = (SearchCriteria)xmlSerializer.Deserialize(reader);
        }
        */
    }
}
